/* **********************************************************
 * Copyright 2010 Dimitar Vangelovski
 *
 * This file is part of the Gens3 3D Engine.
 *
 * The Gens3 3D Engine is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * The Gens3 3D Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Gens3 3D Engine; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 * **********************************************************/


#include "common.h"
#include "3DMath.h"

CVector::CVector()
: x(0.0f)
, y(0.0f)
, z(0.0f)
{
}

CVector::CVector(const CVector &other)
{
	x = other.x;
	y = other.y;
	z = other.z;
}

CVector::CVector(float x2, float y2, float z2)
{
	x = x2;
	y = y2;
	z = z2;
}

CVector::~CVector()
{
}

//////////////////////////////////////////////////////////////////////////////////////////////////


CVector ClosestPointOnLine(CVector vA, CVector vB, CVector vPoint)
{
	CVector vVector1 = vPoint - vA;

	// calculate the line vector as a direction
	CVector vVector2 = Normalize(vB - vA);

	// project vVector1 onto vVector2 (the distance from point vA)
    float t = Dot(vVector2, vVector1);

	if (t <= 0)
	{
		return vA;
	}
	else if (t >= Distance(vA, vB))
	{
		return vB;
	}
	else
	{
		// calculate the vector to add to point vA to find the closest point
		CVector vVector3 = vVector2 * t;

		// calculate the closest point
		CVector vClosestPoint = vA + vVector3;

		return vClosestPoint;
	}
}

bool EdgeSphereCollision(CVector &vCenter, CVector vPolygon[], int vertexCount, float radius, CVector &vEdgeBounce, CVector &vEdgeNormal)
{
	CVector vPoint;

	// go through all of the vertices in the polygon
	for (int i = 0; i < vertexCount; i++)
	{
		// calculate the closest point on the current edge to the center of the sphere
		vPoint = ClosestPointOnLine(vPolygon[i], vPolygon[(i + 1) % vertexCount], vCenter);

		// calculate the distance between the closest point and the center of the sphere
		float fDistance = Distance(vPoint, vCenter);

		// if the distance is less than the radius there is a collision
		if (fDistance < radius)
        {
			// calculate a tangent normal
			CVector vecTmp = vCenter - vPoint;
			float r2 = radius * radius;
			CVector vTangentNormal = (vecTmp * 2.0f) / r2;

			//vEdgeNormal = Normalize(vecTmp);	// this is used to interpolate the bounce vector and the normal to get more pleasing edge bounce vector
								// for this to work, all vertices should have a normal computed as an average from all normals from all
								// the triangles sharing that vertex
								// FIXME: implement this!

			// calculate a bounce vector to move the sphere back

			//vEdgeBounce = Normalize(vecTmp) * (radius - fDistance);
			vEdgeBounce = /*Normalize*/(vTangentNormal) * (radius - fDistance);

			return true;
        }
	}

	return false;
}

CVector RotatePointAroundPoint(CVector vOldPoint, CVector vRotCenter, CVector vRotAngle)
{
	float fTemp1, fTemp2;

	// first we translate the old point with a vector that moves the rotation center to the origin (0, 0, 0)
	CVector vOrigin(0.0f, 0.0f, 0.0f);
	CVector vTranslateVector = vOrigin - vRotCenter;
	CVector vTransPoint = vOldPoint + vTranslateVector;
	CVector vTransRotPoint = vTransPoint;

	// then we rotate the point using the provided rotation angles for each axis
	float cx = (float)cos(vRotAngle.x);
	float sx = (float)sin(vRotAngle.x);
	float cy = (float)cos(vRotAngle.y);
	float sy = (float)sin(vRotAngle.y);
	float cz = (float)cos(vRotAngle.z);
	float sz = (float)sin(vRotAngle.z);

	fTemp1 = vTransRotPoint.y * cx - vTransRotPoint.z * sx;
	fTemp2 = vTransRotPoint.z * cx + vTransRotPoint.y * sx;
	vTransRotPoint.y = fTemp1;
	vTransRotPoint.z = fTemp2;

	fTemp1 = vTransRotPoint.z * cy - vTransRotPoint.x * sy;
	fTemp2 = vTransRotPoint.x * cy + vTransRotPoint.z * sy;
	vTransRotPoint.z = fTemp1;
	vTransRotPoint.x = fTemp2;

	fTemp1 = vTransRotPoint.x * cz - vTransRotPoint.y * sz;
	fTemp2 = vTransRotPoint.y * cz + vTransRotPoint.x * sz;
	vTransRotPoint.x = fTemp1;
	vTransRotPoint.y = fTemp2;

	// finally we translate the rotated point back using the same translation vector
	CVector vRotPoint = vTransRotPoint - vTranslateVector;

	return vRotPoint;
}

CVector RotatePointAroundPoint_Matrix(CVector vOldPoint, CVector vRotCenter, CVector vRotAngle)
{
	float vi[4], vo[4];
	float mrx[16], mry[16], mrz[16], mtmp[16], m[16];

	// translate the old point with a vector that moves the rotation center to the origin (0, 0, 0)
	CVector vOrigin(0.0f, 0.0f, 0.0f);
	CVector vTranslateVector = vOrigin - vRotCenter;

	// the rotating vector
	vi[0] = vOldPoint.x + vTranslateVector.x;
	vi[1] = vOldPoint.y + vTranslateVector.y;
	vi[2] = vOldPoint.z + vTranslateVector.z;
	vi[3] = 1.0f;

	// create the transform matrices
	RotationMatrix_X(vRotAngle.x, mrx);
	RotationMatrix_Y(vRotAngle.y, mry);
	RotationMatrix_Z(vRotAngle.z, mrz);

	// create the combined transformation matrix: [m] = [mrx] * [mry] * [mrz]
	MatrixMatrixMult(mrx, mry, mtmp);
	MatrixMatrixMult(mtmp, mrz, m);

	// multiply the final transform matrix to the rotating vector to get a transformed vector
	VectorMatrixMult(vi, m, vo);

	// translate the rotated point back using the same translation vector
	return CVector(vo[0] - vTranslateVector.x, vo[1] - vTranslateVector.y, vo[2] - vTranslateVector.z);
}
